#pragma once

#include <string>
#include <fstream>
#include <assert.h>
#include <vector>
#include <string.h>
#include <stdio.h>
#include <mutex>

class udmabuf
{
public:
    udmabuf();
    explicit udmabuf(std::string name);

    ~udmabuf();

    udmabuf(udmabuf&&) = default;
    udmabuf(const udmabuf&) = delete;
    udmabuf& operator=(udmabuf&&) = default;
    void operator=(const udmabuf&) = delete;

    std::ifstream get_prop(std::string name) const
    {
        std::string path = (m_class_path + "/").append(name);
        return std::ifstream(path);
    }

    std::ofstream set_prop(std::string name) const
    {
        std::string path = (m_class_path + "/").append(name);
        return std::ofstream(path);
    }

    void sync_for_cpu(unsigned long offset, unsigned long length) const;

    const void *get() const;
    void* get();
    size_t size() const;

private:
    void map();

private:
    int m_fd = -1;
    std::string m_name;
    std::string m_dev_name;
    std::string m_class_path;
    size_t      m_phys_addr = 0;
    size_t      m_size = 0;
    void*       m_ptr = nullptr;
};

struct memory
{
    void* start = nullptr;
    size_t bytesused = 0;
    size_t length = 0;
};

struct cma_frame_allocator
{
    constexpr static size_t single_buffer_size = 4096*2160*3/2;
    constexpr static size_t offsets[4] = {
        0 * single_buffer_size,
        1 * single_buffer_size,
        2 * single_buffer_size,
        3 * single_buffer_size
    };

    udmabuf dmabuf;
    uint8_t* ptr = nullptr;
    std::mutex ptr_end_mutex;
    uint8_t* ptr_end = nullptr;
    uint8_t* buf[4];

    explicit cma_frame_allocator(size_t buffers)
        : dmabuf("udmabuf0")
    {
        assert(buffers <= 4);

        ptr = reinterpret_cast<uint8_t*>(dmabuf.get());
        ptr_end=ptr;

        buf[0] = ptr + 0 * single_buffer_size;
        buf[1] = ptr + 1 * single_buffer_size;
        buf[2] = ptr + 2 * single_buffer_size;
        buf[3] = ptr + 3 * single_buffer_size;
        // Write once to allocate physical memory to u-dma-buf virtual space.
        // Note: Do not use memset() for this.
        //       Because it does not work as expected.
        {
            uint64_t* word_ptr = reinterpret_cast<uint64_t*>(ptr);
            size_t    words    = 4*single_buffer_size/sizeof(uint64_t);
            for(int i = 0 ; i < words; i++) {
                word_ptr[i] = 0;
            }
        }
    }

    void set_buffers_count(size_t count)
    {

    }

    void set_num_planes(size_t num_planes)
    {
        plane_size.resize(num_planes);
    }

    void set_plane_size(size_t plane, size_t size)
    {
        plane_size[plane] = size;
    }

    void set_format(uint32_t width, uint32_t height, uint32_t pix_fmt)
    {

    }

    void aquire(size_t index, memory *mem, size_t num_mem)
    {
        memset(mem, 0, num_mem*sizeof(*mem));
        assert(num_mem == 1);

        mem[0].start = buf[index];
        mem[0].length = plane_size[0];
    }

    uint8_t * aquire_buf(size_t buf_size)
    {
       uint8_t *buf_start=nullptr;
       if((buf_size%sizeof(uint64_t))!=0){
            perror("u dma buf align 64 erro \r\n");
       }
//       printf("u dma buf buf_size:%d\r\n",buf_size);
       ptr_end_mutex.lock();
       buf_start=ptr_end;
       ptr_end=ptr_end+buf_size;
       ptr_end_mutex.unlock();
       uint64_t* word_ptr = reinterpret_cast<uint64_t*>(buf_start);
       for(int i = 0 ; i < buf_size/sizeof(uint64_t); i++) {
           word_ptr[i] = 0;
       }
       return buf_start;
    }

    void release(size_t index, memory *mem, size_t num_mem)
    {
        // sync buffer
        dmabuf.sync_for_cpu(offsets[index], single_buffer_size);

        // now, access them!
    }

    std::vector<size_t> plane_size;
};


